#version 450 core

layout (location = 0) out vec4 FragColor;

struct VertexOutput
{
	vec3 Normal;
	vec4 Color;
	vec2 TexCoord;
    float TilingFactor;
    vec4 ShadowCoord;
};


layout (location = 0) in VertexOutput Output;
layout (location = 5) in float v_TexIndex;
layout (location = 6) in flat int v_EntityID;
layout (location = 7) in vec3 WorldPos;

struct Material
{ 
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    float shininess;
};

uniform sampler2D texture_diffuse1;//albedoMap
uniform sampler2D texture_diffuse2;
uniform sampler2D texture_diffuse3;
uniform sampler2D texture_specular1;//metallicMap
uniform sampler2D texture_specular2;
uniform sampler2D texture_normal1;
uniform sampler2D texture_height1;//normalMap

uniform sampler2D texture_shininess_roughness1;
uniform sampler2D texture_opacity_ao1;
uniform sampler2D shadowMap;
uniform sampler2D shadowMapTransparent;

layout (location = 7) uniform int hasColorMaterial;
layout (location = 8) uniform Material material;
layout (location = 12) uniform vec4 lightPosition;//direction
layout (location = 13) uniform vec3 lightColor;
layout (location = 14) uniform vec3 cameraPos;
layout (location = 15) uniform int enableLight;
layout (location = 16) uniform float lightIntensity;

//uniform sampler2D albedoMap;
//uniform sampler2D normalMap;
//uniform sampler2D metallicMap;
//uniform sampler2D roughnessMap;
//uniform sampler2D aoMap;
layout (location = 17) uniform mediump vec2 TextureSizeShadown;
layout (location = 19) uniform lowp float BlurRadius; 
layout (location = 20) uniform mediump vec2 BlurDirection;
layout (location = 22) uniform int enableDisplayShadow;

const float PI = 3.14159265359;

float DistributionGGX(vec3 N, vec3 H, float roughness)
{
    float a = roughness*roughness;
    float a2 = a*a;
    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;

    float nom   = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;

    return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySchlickGGX(float NdotV, float roughness)
{
    float r = (roughness + 1.0);
    float k = (r*r) / 8.0;

    float nom   = NdotV;
    float denom = NdotV * (1.0 - k) + k;

    return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
    float ggx1 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
float pcfShadow(mediump vec2 coord, sampler2D depthTexture, float currentDepth, float bias, float strength)
{
    float shadow = 0.0;
    vec2 texelSize = 1.0/TextureSizeShadown * 1.0;

    for(int x = -1; x <= 1; ++x)
    {
        for(int y = -1; y <= 1; ++y)
        {
            float pcfDepth = texture(depthTexture, coord + vec2(x, y) * texelSize).r;
            if(currentDepth - bias > pcfDepth)
            {
                shadow += strength;                  
            }      
        }    
    }

    
    shadow /= 9.0; 

    return shadow;
}

void main()
{

    vec3 lightColorCom = lightColor * lightIntensity;

    vec3 N = normalize(Output.Normal);
    vec3 V = normalize(cameraPos - WorldPos);//viewDir

    vec3 albedo     = vec3(0.0,0.0,0.0);
    vec3 normal     = vec3(0.0,0.0,0.0);
    float metallic  = 0.0;
    float roughness = 0.0;
    float ao        = 1.0;

    vec4 ambientColor = vec4(0.1, 0.1, 0.1, 1.0);
    vec4 diffuseColor = vec4(0.0);
    vec4 specularColor = vec4(0.0);
    vec4 texColor = vec4(0.0);

    vec3 ambient = vec3(0.0);
    vec3 color = vec3(0.0);
    float ambientColorRatio = 0.8;
    float diffuseColorRatio = 0.2;
    float specularColorRatio = 0.2;

    // calculate per-light radiance
    vec3 L = vec3(0.0);
    if(lightPosition.w == 0.0){
        //direct light
        L = normalize(lightPosition.xyz);
        ambientColorRatio = 0.1;
        diffuseColorRatio = 1.0;
    }
    else{
        //point light
        L = normalize(lightPosition.xyz - WorldPos);
    }
    
    vec3 H = normalize(V + L);//halfwayDir
    float distance = length(lightPosition.xyz - WorldPos) + 0.001;
    float attenuation = 1.0 / (distance * distance);
    vec3 radiance = lightColorCom * attenuation;

    float spec = 0.0;

    // scale light by NdotL
    float NdotL = max(dot(N, L), 0.0);   //cosTheta |  diff
    float cosPhi = max(dot(V, reflect(-L, N)), 0.0);

    //spec = pow(cosPhi, 32.0);//phong
    spec = pow(max(dot(N, H), 0.0), 8.0);//blinn phong

    // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0 
    // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)    
   
    vec3 F0 = vec3(0.04); 
    // reflectance equation
    vec3 Lo = vec3(0.0);


    if(hasColorMaterial != 0)
    {
        ambientColor = material.ambient;
        diffuseColor = material.diffuse;
        specularColor = material.specular;  
		
		if(enableLight != 0){
            ambientColorRatio = 0.8;
            diffuseColorRatio = 0.5;
			color =  lightColorCom * diffuseColor.rgb * ambientColorRatio + \
			lightColorCom * diffuseColor.rgb * NdotL * diffuseColorRatio + \
			lightColorCom * specularColor.rgb *spec*specularColorRatio;
		}
		else{
			color = diffuseColor.rgb;
		}
        //color = diffuseColor.rgb;
    }
    else{

        if(enableLight != 0){
            switch(int(v_TexIndex))
            {
                case  0: 
                    diffuseColor = texture(texture_diffuse1, Output.TexCoord * Output.TilingFactor); 
                    ambientColor = diffuseColor;
                    color = lightColorCom * ambientColor.rgb * ambientColorRatio + lightColorCom * diffuseColor.rgb * NdotL * diffuseColorRatio + lightColorCom *spec*specularColorRatio;
                    //color = diffuseColor.rgb;
                    break;
                case  1: 
                    diffuseColor = texture(texture_diffuse1, Output.TexCoord * Output.TilingFactor);
                    ambientColor = diffuseColor;
                    specularColor = texture(texture_specular1, Output.TexCoord * Output.TilingFactor); 
                    color = lightColorCom * ambientColor.rgb * ambientColorRatio + lightColorCom * diffuseColor.rgb * NdotL + \
                    lightColorCom * specularColor.rgb * pow(cosPhi, material.shininess*3.0);
                    break;
                case  2: 
                    diffuseColor = texture(texture_diffuse1, Output.TexCoord * Output.TilingFactor);
                    ambientColor = diffuseColor;
                    specularColor = texture(texture_specular1, Output.TexCoord * Output.TilingFactor); 
                    color = lightColorCom * ambientColor.rgb * ambientColorRatio + lightColorCom * diffuseColor.rgb * NdotL + \
                    lightColorCom * specularColor.rgb * pow(cosPhi, 3.0);
                    break;
                case 5:
                    albedo     = pow(texture(texture_diffuse1, Output.TexCoord * Output.TilingFactor).rgb, vec3(2.2));
                    normal     = texture(texture_height1, Output.TexCoord * Output.TilingFactor).rgb;//getNormalFromNormalMap();
                    metallic  = texture(texture_specular1, Output.TexCoord * Output.TilingFactor).r;
                    roughness = texture(texture_shininess_roughness1, Output.TexCoord * Output.TilingFactor).r;
                    ao        = texture(texture_opacity_ao1, Output.TexCoord * Output.TilingFactor).r;

                    F0 = mix(F0, albedo, metallic);

                      // Cook-Torrance BRDF
                    float NDF = DistributionGGX(N, H, roughness);   
                    float G   = GeometrySmith(N, V, L, roughness);      
                    vec3 F    = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
                    
                    vec3 numerator    = NDF * G * F; 
                    float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zero
                    vec3 specular = numerator / denominator;
                
                    // kS is equal to Fresnel
                    vec3 kS = F;
                    // for energy conservation, the diffuse and specular light can't
                    // be above 1.0 (unless the surface emits light); to preserve this
                    // relationship the diffuse component (kD) should equal 1.0 - kS.
                    vec3 kD = vec3(1.0) - kS;
                    // multiply kD by the inverse metalness such that only non-metals 
                    // have diffuse lighting, or a linear blend if partly metal (pure metals
                    // have no diffuse light).
                    kD *= 1.0 - metallic;	

                    // add to outgoing radiance Lo
                    Lo += (kD * albedo / PI + specular) * radiance * NdotL;  // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again

                    // ambient lighting (note that the next IBL tutorial will replace 
                    // this ambient lighting with environment lighting).
                    ambient = vec3(0.03) * albedo * ao; 
                    color = ambient + Lo; 

                    // HDR tonemapping
                    color = color / (color + vec3(1.0));
                    // gamma correct
                    color = pow(color, vec3(1.0/2.2)); 
                   // color = vec3(0.0,1.0,1.0);
                   //color = texture(texture_diffuse1, Output.TexCoord * Output.TilingFactor).rgb;
                    diffuseColor.a = 1.0;

                    break;
                }
            }
            else{
                 switch(int(v_TexIndex))
                {
                    case  0: 
                        //diffuseColor = gaussianBlur(Output.TexCoord * Output.TilingFactor, BlurDirection, texture_diffuse1);
                        diffuseColor = texture(texture_diffuse1, Output.TexCoord * Output.TilingFactor);                        
                        color = diffuseColor.rgb;
                        break;
                }
            }
    }
    

    //shadow
    float visibility = 1.0;

    if(enableDisplayShadow != 0)
    {
        vec3 projCoords = Output.ShadowCoord.xyz / Output.ShadowCoord.w;
        
        float currentDepth = projCoords.z;

        //float bias = 0.01;
        float bias = max(0.05 * (1.0 - dot(Output.Normal, lightPosition.xyz)), 0.005);

        float s = pcfShadow(projCoords.xy, shadowMapTransparent, currentDepth, bias, 0.5);
        s += pcfShadow(projCoords.xy, shadowMap, currentDepth, bias, 1.0);
        visibility = 1.0 - s;
    }
    

    texColor = vec4(color, 1.0) * visibility;
    texColor.a = diffuseColor.a;

    if (texColor.a == 0.0)
		discard;

    FragColor = texColor;

}